package com.mxx.common.generator;

import com.mxx.common.generator.db.Table;
import com.mxx.common.utils.Strings;
import lombok.Data;
import lombok.experimental.Accessors;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Data
@Accessors(chain = true)
public class EntityInfo {
    private Table table;
    private String entityName;
    private String entityClass;
    private String entityPackage;
    private Boolean generate;
    private String remark;
    private boolean classes;
    private Constant.EntityType entityType;
    private Constant.EntityState entityState = Constant.EntityState.NONE;
    private Constant.EntityVisible entityVisible = Constant.EntityVisible.PUBLIC;
    private EntityInfo superClass;
    private String wholeModifyStr;
    private String superInterfaceStr;
    private List<EntityInfo> generic = new ArrayList<>();

    private List<EntityField> entityFieldList = new ArrayList<>();
    private List<EntityField> superEntityField = new ArrayList<>();
    private List<EntityField> genFieldList = new ArrayList<>();

    private List<EntityInfo> superInterface = new ArrayList<>();
    private List<EntityInfo> genInterfaceList = new ArrayList<>();

    private Set<String> importPackage = new HashSet<>();
    private Set<EntityAnno> annotationSet = new HashSet<>();
    private Set<EntityInfo> innerClass = new HashSet<>();


    public EntityInfo(Constant.EntityType entityType) {
        this.generate = true;
        this.entityType = entityType;
    }

    public EntityInfo(Class classes) {
        this.generate = false;
        this.entityName = classes.getSimpleName();
        this.entityClass = classes.getName();

        if (classes.isAnnotation()) {
            this.entityType = (Constant.EntityType.ANNOTATION);
        } else if (classes.isInterface()) {
            this.entityType = (Constant.EntityType.INTERFACE);
        } else if (classes.isEnum()) {
            this.entityType = (Constant.EntityType.ENUM);
        }
        int modifiers = classes.getModifiers();
        if (Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers)) {
            this.entityState = (Constant.EntityState.FINAL_STATIC);
        } else if (Modifier.isFinal(modifiers)) {
            this.entityState = (Constant.EntityState.FINAL);
        } else if (Modifier.isStatic(modifiers)) {
            this.entityState = (Constant.EntityState.STATIC);
        } else if (Modifier.isAbstract(modifiers)) {
            this.entityState = (Constant.EntityState.ABSTRACT);
        }
        if (Modifier.isProtected(modifiers)) {
            this.entityVisible = (Constant.EntityVisible.PROTECTED);
        } else if (Modifier.isPrivate(modifiers)) {
            this.entityVisible = (Constant.EntityVisible.PRIVATE);
        }

        Class supperClass = classes.getSuperclass();
        this.superClass = supperClass == null ? null : new EntityInfo(supperClass);
        Class[] superInterfaceArray = classes.getInterfaces();
        for (Class superInterface : superInterfaceArray) {
            this.superInterface.add(new EntityInfo(superInterface));
        }
        Field[] fields = classes.getDeclaredFields();
        for (Field field : fields) {
            int fieldModifiers = field.getModifiers();
            if (Modifier.isPrivate(fieldModifiers) && !Modifier.isFinal(modifiers) && !Modifier.isStatic(fieldModifiers)) {
                this.superEntityField.add(new EntityField(field));
            }
        }
    }


    public EntityInfo setEntityType(Constant.EntityType entityType) {
        Assert.notFalse(generate, "This class cannot be modified");
        if (this.entityType != entityType) {
            switch (entityType) {
                case INTERFACE:
                case ENUM:
                    this.entityState = Constant.EntityState.NONE;
            }
            this.entityType = entityType;
        }
        return this;
    }

    public EntityInfo setEntityState(Constant.EntityState entityState) {
        Assert.notTrue(generate, "This class cannot be modified");
        if (this.entityState != entityState) {
            switch (entityState) {
                case ABSTRACT:
                case FINAL_STATIC:
                case FINAL:
                case STATIC:
                    Assert.notFalse(Strings.anyMatch(this.entityType, Constant.EntityType.ENUM, Constant.EntityType.INTERFACE),
                            "This class does not support this pattern");
            }
            this.entityState = entityState;
        }
        return this;
    }

    public EntityInfo setEntityVisible(Constant.EntityVisible entityVisible) {
        Assert.notTrue(generate, "This class cannot be modified");
        this.entityVisible = entityVisible;
        return this;
    }

    public EntityInfo setEntityPackage(String entityPackage) {
        this.entityPackage = entityPackage;
        return this;
    }

    public EntityInfo flushEntityClass() {
        Assert.notFalse(Strings.anyMatch(null, entityName, entityPackage), "entityName or entityPackage is null");
        this.entityClass = entityPackage + "." + entityName;
        return this;
    }

    public EntityInfo addEntityField(EntityField field) {
        this.entityFieldList.add(field);
        field.setEntityInfo(this);
        addImportPackage(field.getFieldClass());
        return this;
    }

    public EntityInfo addAnno(EntityAnno entityAnno) {
        this.annotationSet.add(entityAnno);
        String annotationClass = entityAnno.getAnnotationClass();
       addImportPackage(annotationClass);
        return this;
    }
    public EntityInfo addImportPackage(String classes) {
        if (!classes.startsWith("java.lang") && !classes.startsWith(entityPackage)) {
            this.importPackage.add(classes);
        }
        return this;
    }
    public EntityInfo addSuperInterface(EntityInfo entityInfo) {
        this.superInterface.add(entityInfo);
        this.importPackage.add(entityInfo.entityClass);
        return this;
    }

    public EntityInfo setSuperClass(EntityInfo entityInfo) {
        this.superClass = entityInfo;
        this.importPackage.add(entityInfo.entityClass);
        this.superEntityField.addAll(entityInfo.entityFieldList);
        this.superEntityField.addAll(entityInfo.superEntityField);
        return this;
    }

    public EntityInfo refreshRemark(Function<String, String> function) {
        this.remark = function.apply(this.remark == null ? "" : this.remark);
        return this;
    }

    public void afterPropertiesSet() {
        this.wholeModifyStr = this.entityVisible.getText() + this.entityState.getText() + this.entityType.getText();
        this.superInterfaceStr = this.superInterface.stream().map(p -> p.getEntityName()).collect(Collectors.joining(", "));
        Set<String> filterSet = new HashSet<>();
        this.superEntityField.forEach(p -> filterSet.add(p.getFieldName()));
        this.genFieldList.clear();
        for (EntityField entityField : entityFieldList) {
            if (!filterSet.contains(entityField.getFieldName())) {
                this.genFieldList.add(entityField);
                for (EntityAnno entityAnno : entityField.getAnnotationSet()){
                    addImportPackage(entityAnno.getAnnotationClass());
                }

            }
        }

        filterSet.clear();
    }


    @Override
    public boolean equals(Object obj) {
        if (obj instanceof EntityInfo) {
            return this.entityName.equals(((EntityInfo) obj).entityName);
        }
        return super.equals(obj);
    }

    @Override
    public int hashCode() {
        return Objects.hash(entityName);
    }

    @Override
    public String toString() {
        return entityClass + (Strings.isEmpty(remark) ? "" : " (" + remark + ")");
    }

}
